using System;
using System.Collections;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using csammisrun.OscarLib.Utility;

namespace csammisrun.OscarLib
{
  #region Logged in / out exceptions
  /// <summary>
  /// Thrown when an operation is requested that requires the <see cref="Session"/>
  /// to be logged in
  /// </summary>
  public class NotLoggedInException : Exception
  {
    /// <summary>
    /// Creates a new NotLoggedInException
    /// </summary>
    public NotLoggedInException()
    {
    }
  }

  /// <summary>
  /// Thrown when an operation is requested that requires the <see cref="Session"/>
  /// to be logged out
  /// </summary>
  public class LoggedInException : Exception
  {
    private string _message;

    /// <summary>
    /// Creates a new LoggedInException with a blank message
    /// </summary>
    public LoggedInException()
    {
      _message = "";
    }

    /// <summary>
    /// Creates a new LoggedInException
    /// </summary>
    /// <param name="message">The message to be passed with the exception</param>
    public LoggedInException(string message)
    {
      _message = message;
    }

    /// <summary>
    /// Gets a message that describes the current exception
    /// </summary>
    public override string Message
    {
      get
      {
        return _message;
      }
    }
  }
  #endregion

  /// <summary>
  /// The representation of an AOL Instant Messenger or ICQ session
  /// </summary>
  public class Session
  {
    ConnectionManager _connman;
    SSIManager _ssiman;

    FamilyManager _famman = new FamilyManager();
    RateClassManager _rateman = new RateClassManager();
    LimitManager _limitman = new LimitManager();
    RendezvousManager _rdman = new RendezvousManager();

    uint _requestID = 0;
    ushort _loginport = 5190;
    string _screenname = "";
    string _password = "";
    Hashtable _requestidstorage = new Hashtable();
    bool _loggedin = false;
    Capabilities _caps = Capabilities.OscarLib;
    PrivacySetting _privacy;
    OSCARIdentification _clientid = new OSCARIdentification();

    //NetworkProxy _proxytype = NetworkProxy.None;
    //string _proxyserver = "";
    //ushort _proxyport = 0;
    //string _proxyusername = "";
    //string _proxypassword = "";

    string _profile = "";
    System.Text.Encoding _profileenc;
    string _available = "";
    string _scratchpath = "";

    ushort _parametercount = 0;
    bool _publicidletime;

    string _iconfilename = "";

    static Session _ref = null;

    #region Public events and protected event firing functions
    #region OscarLib-generated events
    /// <summary>
    /// Occurs when the library generates a status update message
    /// </summary>
    public event InformationMessageHandler StatusUpdate;
    /// <summary>
    /// Raises the <see cref="StatusUpdate"/> event
    /// </summary>
    /// <param name="message">A status message</param>
    protected internal void OnStatusUpdate(string message)
    {
      if (this.StatusUpdate != null)
        this.StatusUpdate(this, message);
    }

    /// <summary>
    /// Occurs when the library generates a status update message during login
    /// </summary>
    public event LoginStatusUpdateHandler LoginStatusUpdate;
    /// <summary>
    /// Raises the <see cref="LoginStatusUpdate"/> event
    /// </summary>
    /// <param name="message">A status message</param>
    /// <param name="percentdone">The percentage of the login progress that has been completed</param>
    protected internal void OnLoginStatusUpdate(string message, double percentdone)
    {
      if (this.LoginStatusUpdate != null)
      {
        this.LoginStatusUpdate(this, message, percentdone);
      }
    }

    /// <summary>
    /// Occurs when the library generates a warning message
    /// </summary>
    public event WarningMessageHandler WarningMessage;
    /// <summary>
    /// Raises the <see cref="WarningMessage"/> event
    /// </summary>
    /// <param name="message">A warning message</param>
    protected internal void OnWarning(ServerErrorCode errorcode)
    {
      if (this.WarningMessage != null)
        this.WarningMessage(this, errorcode);
    }

    /// <summary>
    /// Occurs when the library generates an error message
    /// </summary>
    public event ErrorMessageHandler ErrorMessage;
    /// <summary>
    /// Raises the <see cref="ErrorMessage"/> event or the <see cref="LoginFailed"/> event
    /// </summary>
    /// <param name="message">An error message</param>
    /// <remarks>If the login process has not completed, <see cref="LoginFailed"/> is raised.
    /// Otherwise, <see cref="ErrorMessage"/> is raised.</remarks>
    protected internal void OnError(ServerErrorCode errorcode)
    {
      if (!_loggedin)
      {
        if (this.LoginFailed != null)
        {
          if (errorcode == ServerErrorCode.LostBOSConnection)
            this.LoginFailed(this, LoginErrorCode.CantReachBOSServer);
          else
            this.LoginFailed(this, LoginErrorCode.UnknownError);
        }
      }
      else
      {
        if (this.ErrorMessage != null)
          this.ErrorMessage(this, errorcode);
      }
    }
    #endregion

    #region SNAC01 events
    /// <summary>
    /// Occurs when the login process is complete.
    /// </summary>
    public event LoginCompletedHandler LoginCompleted;
    /// <summary>
    /// Raises the <see cref="LoginCompleted"/> event
    /// </summary>
    protected internal void OnLoginComplete()
    {
      LoggedIn = true;
      if (this.LoginCompleted != null)
        this.LoginCompleted(this);
    }

    /// <summary>
    /// Occurs when a remote client has warned this client
    /// </summary>
    public event WarningReceivedHandler WarningReceived;
    /// <summary>
    /// Raises the <see cref="WarningReceived"/> event.
    /// </summary>
    /// <param name="newlevel">The client's new warning level</param>
    /// <param name="anonymous"><c>true</c> if this warning was sent anonymously, <c>false</c> otherwise</param>
    /// <param name="ui">A <see cref="UserInfo"/> structure describing the warning user. If <paramref name="anonymous"/> is
    /// <c>true</c>, this structure is unpopulated</param>
    protected internal void OnWarningReceived(ushort newlevel, bool anonymous, UserInfo ui)
    {
      if (this.WarningReceived != null)
        this.WarningReceived(this, newlevel, anonymous, ui);
    }

    /// <summary>
    /// Occurs when the server sends the MOTD at signon
    /// </summary>
    public event MessageOfTheDayReceivedHandler MessageOfTheDayReceived;
    /// <summary>
    /// Raises the <see cref="MessageOfTheDayReceived"/> event
    /// </summary>
    /// <param name="motdtype">The type of the MOTD</param>
    /// <param name="message">The message string</param>
    protected internal void OnMessageOfTheDayReceived(ushort motdtype, string message)
    {
      if (this.MessageOfTheDayReceived != null)
        this.MessageOfTheDayReceived(this, motdtype, message);
    }
    #endregion

    #region SNAC02 events
    /// <summary>
    /// Occurs when a response to a request for user information is received
    /// </summary>
    public event UserInfoReceivedHandler UserInfoReceived;
    /// <summary>
    /// Raises the <see cref="UserInfoReceived"/> event
    /// </summary>
    /// <param name="info">A <see cref="UserInfoResponse"/> structure describing the user</param>
    protected internal void OnUserInfoReceived(UserInfoResponse info)
    {
      //_userinfo[info.Info.ScreenName] = info;
      if (this.UserInfoReceived != null)
        this.UserInfoReceived(this, info);
    }

    /// <summary>
    /// Occurs when the server sends acknowledgement of a directory update request
    /// </summary>
    public event DirectoryUpdateAcknowledgedHandler DirectoryUpdateAcknowledged;
    /// <summary>
    /// Raises the <see cref="DirectoryUpdateAcknowledged"/> event
    /// </summary>
    /// <param name="success"><c>true</c> if the directory update succeded, and <c>false</c> otherwise</param>
    protected internal void OnDirectoryUpdateAcknowledged(bool success)
    {
      if (this.DirectoryUpdateAcknowledged != null)
        this.DirectoryUpdateAcknowledged(this, success);
    }
    #endregion

    #region SNAC03 events
    /// <summary>
    /// Occurs when a user status notification is received
    /// </summary>
    public event UserStatusReceivedHandler UserStatusReceived;
    /// <summary>
    /// Raises the <see cref="UserStatusReceived"/> event
    /// </summary>
    /// <param name="userinfo">A <see cref="UserInfo"/> block describing the user</param>
    protected internal void OnUserStatusReceived(UserInfo userinfo)
    {
      if (this.UserStatusReceived != null)
        this.UserStatusReceived(this, userinfo);
    }

    /// <summary>
    /// Occurs when a user on the client's contact list has gone offline
    /// </summary>
    public event UserOfflineHandler UserOffline;
    /// <summary>
    /// Raises the <see cref="UserOffline"/> event
    /// </summary>
    /// <param name="userinfo">A <see cref="UserInfo"/> block describing the user</param>
    protected internal void OnUserOffline(UserInfo userinfo)
    {
      if (this.UserOffline != null)
        this.UserOffline(this, userinfo);
    }
    #endregion

    #region SNAC04 events
    /// <summary>
    /// Occurs when the server sends a typing notification
    /// </summary>
    public event TypingNotificationHandler TypingNotification;
    /// <summary>
    /// Raises the <see cref="TypingNotification"/> event
    /// </summary>
    /// <param name="screenname">The screen name sending the typing notification</param>
    /// <param name="notification">The typing notification being sent</param>
    protected internal void OnTypingNotification(string screenname, TypingNotification notification)
    {
      if (this.TypingNotification != null)
        this.TypingNotification(this, screenname, notification);
    }

    /// <summary>
    /// Occurs when an instant message is received.
    /// </summary>
    public event MessageReceivedHandler MessageReceived;
    /// <summary>
    /// Raises the <see cref="MessageReceived"/> event
    /// </summary>
    /// <param name="message">The <see cref="IM"/> that was received</param>
    protected internal void OnMessageReceived(IM message)
    {
      if (this.MessageReceived != null)
        this.MessageReceived(this, message);
    }

    /// <summary>
    /// Occurs when a file transfer request is received
    /// </summary>
    public event FileTransferRequestReceivedHandler FileTransferRequestReceived;
    /// <summary>
    /// Occurs when a Direct IM transfer request is received
    /// </summary>
    public event DirectIMRequestReceivedHandler DirectIMRequestReceived;

    /// <summary>
    /// Raises the <see cref="FileTransferRequestReceived"/> event
    /// </summary>
    /// <param name="key">The unique key needed to respond to this request</param>
    protected internal void OnDirectConnectionRequestReceived(byte[] key)
    {
      DirectConnection conn = Connections.GetDirectConnectionByCookie(key);

      if (conn is FileTransferConnection && this.FileTransferRequestReceived != null)
      {
        FileTransferConnection ftc = conn as FileTransferConnection;
        this.FileTransferRequestReceived(this, ftc.Other, ftc.VerifiedIP, ftc.FileHeader.Name,
          ftc.TotalFileSize, ftc.Message, key);
      }
      //else if (rd.DirectConnection.ConnectionType == DirectConnectType.DirectIM &&
      //  this.DirectIMRequestReceived != null)
      //{
      //  this.DirectIMRequestReceived(this, rd.UserInfo);
      //}
    }
    
    /// <summary>
    /// Occurs when a chat room invitation is received
    /// </summary>
    public event ChatInvitationReceivedHandler ChatInvitationReceived;
    /// <summary>
    /// Raises the <see cref="ChatInvitationReceived"/> event
    /// </summary>
    /// <param name="sender">A <see cref="UserInfo"/> object represnting the inviter</param>
    /// <param name="roomname">The name of the chatroom</param>
    /// <param name="message">An invitation chatroom</param>
    /// <param name="encoding">The text encoding used in the chatroom</param>
    /// <param name="language">The language used in the chatroom</param>
    /// <param name="key">The unique key needed to respond to this request</param>
    protected internal void OnChatInvitationReceived(UserInfo sender,
      string roomname,
      string message,
      Encoding encoding,
      string language,
      byte[] key)
    {
      if (this.ChatInvitationReceived != null)
        this.ChatInvitationReceived(this, sender, roomname, message, encoding, language, key);
    }

    /// <summary>
    /// Occurs when the server sends an acknowledgement for a warning request
    /// </summary>
    public event WarningAcknowledgementHandler WarningAcknowledgement;
    /// <summary>
    /// Raises the <see cref="WarningAcknowledgement"/> event
    /// </summary>
    /// <param name="requestid">The request ID of the SNAC requesting the warning level change</param>
    /// <param name="warning_level_gain">The remote client's rise in warning level</param>
    /// <param name="new_warning_level">The remote client's new warning level</param>
    protected internal void OnWarningAcknowledgement(uint requestid,
      ushort warning_level_gain,
      ushort new_warning_level)
    {
      string screenname = (string)RetrieveRequestID(requestid);
      if (this.WarningAcknowledgement != null)
        this.WarningAcknowledgement(this, screenname, warning_level_gain, new_warning_level);
    }

    /// <summary>
    /// Occurs when the server sends an undeliverable message notification
    /// </summary>
    /// <remarks>
    /// This event is raised when a remote client has attempted to send a message
    /// to your client, but it could not be delivered
    /// </remarks>
    public event UndeliverableMessageHandler UndeliverableMessage;
    /// <summary>
    /// Raises the <see cref="UndeliverableMessage"/> event
    /// </summary>
    /// <param name="channel">The channel of the message</param>
    /// <param name="userinfo">A <see cref="UserInfo"/> block describing the sender</param>
    /// <param name="numbermissed">The number of missed message from this sender</param>
    /// <param name="reason">A <see cref="UndeliverableMessageReason"/> describing the reason for failure</param>
    protected internal void OnUndeliverableMessage(
      ushort channel,
      UserInfo userinfo,
      ushort numbermissed,
      UndeliverableMessageReason reason)
    {
      if (this.UndeliverableMessage != null)
        this.UndeliverableMessage(this, channel, userinfo, numbermissed, reason);
    }

    /// <summary>
    /// Occurs when the server sends acknowledgement that a message was accepted for delivery
    /// </summary>
    public event MessageAcceptedHandler MessageAccepted;
    /// <summary>
    /// Raises the <see cref="MessageAccepted"/> event
    /// </summary>
    /// <param name="channel">The channel of the message</param>
    /// <param name="screenname">The screenname to which the message was sent</param>
    protected internal void OnMessageAccepted(ushort channel, string screenname)
    {
      if (this.MessageAccepted != null)
        this.MessageAccepted(this, channel, screenname);
    }
    #endregion

    #region SNAC08 events
    /// <summary>
    /// Occurs when the server sends a popup message
    /// </summary>
    public event PopupMessageHandler PopupMessage;
    /// <summary>
    /// Raises the <see cref="PopupMessage"/> event
    /// </summary>
    /// <param name="width">The width of the popup box, in pixels</param>
    /// <param name="height">The height of the popup box, in pixels</param>
    /// <param name="delay">The autohide delay of the popup box, in seconds</param>
    /// <param name="url">The URL associated with the message</param>
    /// <param name="message">The message to display</param>
    protected internal void OnPopupMessage(int width, int height, int delay, string url, string message)
    {
      if (this.PopupMessage != null)
        this.PopupMessage(this, width, height, delay, url, message);
    }
    #endregion

    #region SNAC0A events
    /// <summary>
    /// Occurs when results from a user search are available
    /// </summary>
    public event SearchByEmailResultsHandler SearchByEmailResults;
    /// <summary>
    /// Raises the <see cref="SearchByEmailResults"/> event
    /// </summary>
    /// <param name="email">The email address that was searched for</param>
    /// <param name="results">The screennames that are associated with the email address</param>
    protected internal void OnSearchByEmailResults(string email, string[] results)
    {
      if (this.SearchByEmailResults != null)
        this.SearchByEmailResults(this, email, results);
    }
    #endregion

    #region SNAC0B events
    /// <summary>
    /// Occurs when the server sends the minimum status reporting interval at login
    /// </summary>
    public event ReportingIntervalReceivedHandler ReportingIntervalReceived;
    /// <summary>
    /// Raises the <see cref="ReportingIntervalReceived"/> event
    /// </summary>
    /// <param name="hours">The minimum status reporting interval, in hours</param>
    protected internal void OnReportingIntervalReceived(int hours)
    {
      if (this.ReportingIntervalReceived != null)
        this.ReportingIntervalReceived(this, hours);
    }
    #endregion

    #region SNAC0D events
    /// <summary>
    /// Occurs when a chat room has been successfully created
    /// </summary>
    public event ChatRoomCreatedHandler ChatRoomCreated;

    /// <summary>
    /// Raises the <see cref="ChatRoomCreated"/> event
    /// </summary>
    /// <param name="newroom">The newly created chat room</param>
    /// <remarks>This event is actually triggered from SNAC(01,02)</remarks>
    protected internal void OnChatRoomCreated(ChatRoomInfo newroom)
    {
      if (this.ChatRoomCreated != null)
        this.ChatRoomCreated(this, newroom);
    }
    #endregion

    #region SNAC0E events
    /// <summary>
    /// Occurs when a user has joined a chat room
    /// </summary>
    public event ChatRoomUserJoinedHandler ChatRoomUserJoined;
    /// <summary>
    /// Raises the <see cref="ChatRoomUserJoined"/> event
    /// </summary>
    /// <param name="room">A <see cref="ChatRoomInfo"/> object</param>
    /// <param name="ui">A <see cref="UserInfo"/> block describing the user</param>
    protected internal void OnChatRoomUserJoined(ChatRoomInfo room, UserInfo ui)
    {
      if (this.ChatRoomUserJoined != null)
        this.ChatRoomUserJoined(this, room, ui);
    }

    /// <summary>
    /// Occurs when a user has left a chat room
    /// </summary>
    public event ChatRoomUserLeftHandler ChatRoomUserLeft;
    /// <summary>
    /// Raises the <see cref="ChatRoomUserLeft"/> event
    /// </summary>
    /// <param name="room">A <see cref="ChatRoomInfo"/> object</param>
    /// <param name="ui">A <see cref="UserInfo"/> block describing the user</param>
    protected internal void OnChatRoomUserLeft(ChatRoomInfo room, UserInfo ui)
    {
      if (this.ChatRoomUserLeft != null)
        this.ChatRoomUserLeft(this, room, ui);
    }

    /// <summary>
    /// Occurs when a message is received in a chat room
    /// </summary>
    public event ChatRoomMessageReceivedHandler ChatRoomMessageReceived;
    /// <summary>
    /// Raises the <see cref="ChatRoomMessageReceived"/> event
    /// </summary>
    /// <param name="room">A <see cref="ChatRoomInfo"/> object</param>
    /// <param name="sender">A <see cref="UserInfo"/> object describing the user who sent the message</param>
    /// <param name="message">The message that was sent</param>
    /// <param name="encoding">The text encoding of the message</param>
    /// <param name="language">The language of the message</param>
    protected internal void OnChatRoomMessageReceived(ChatRoomInfo room, UserInfo sender,
      string message, Encoding encoding, string language)
    {
      if (this.ChatRoomMessageReceived != null)
      {
        this.ChatRoomMessageReceived(this, room, sender, message, encoding, language);
      }
    }
    #endregion

    #region SNAC0F events
    /// <summary>
    /// Occurs when the server sends the results of a directory search
    /// </summary>
    public event SearchResultsHandler SearchResults;
    /// <summary>
    /// Raises the <see cref="SearchResults"/> event
    /// </summary>
    /// <param name="results">The results of the directory search</param>
    protected internal void OnSearchResults(DirectoryEntry[] results)
    {
      if (this.SearchResults != null)
        this.SearchResults(this, results);
    }

    /// <summary>
    /// Occurs when the server sends a list of interests
    /// </summary>
    public event InterestsReceivedHandler InterestsReceived;
    /// <summary>
    /// Raises the <see cref="InterestsReceived"/> event
    /// </summary>
    /// <param name="results">The results of the interests request</param>
    protected internal void OnInterestsReceived(InterestItem[] results)
    {
      if (this.InterestsReceived != null)
        this.InterestsReceived(this, results);
    }
    #endregion

    #region SNAC10 events
    /// <summary>
    /// Occurs when a buddy icon upload finishes successfully
    /// </summary>
    public event BuddyIconUploadCompletedHandler BuddyIconUploadCompleted;
    /// <summary>
    /// Raises the <see cref="BuddyIconUploadCompleted"/> event
    /// </summary>
    protected internal void OnBuddyIconUploadCompleted()
    {
      if (this.BuddyIconUploadCompleted != null)
        this.BuddyIconUploadCompleted(this);
    }

    /// <summary>
    /// Occurs when a buddy icon upload fails
    /// </summary>
    public event BuddyIconUploadFailedHandler BuddyIconUploadFailed;
    /// <summary>
    /// Raises the <see cref="BuddyIconUploadFailed"/>
    /// </summary>
    /// <param name="reason">The reason for the failure</param>
    protected internal void OnBuddyIconUploadFailed(string reason)
    {
      if (this.BuddyIconUploadFailed != null)
        this.BuddyIconUploadFailed(this, reason);
    }

    /// <summary>
    /// Occurs when the server sends a buddy icon
    /// </summary>
    public event BuddyIconDownloadCompletedHandler BuddyIconDownloadCompleted;
    /// <summary>
    /// Raises the <see cref="BuddyIconDownloadCompleted"/> event
    /// </summary>
    /// <param name="screenname">The screenname who owns the received icon</param>
    /// <param name="icon">The byte array of the received image</param>
    protected internal void OnBuddyIconDownloadCompleted(string screenname, byte[] icon)
    {
      System.IO.MemoryStream ms = new System.IO.MemoryStream(icon, false);
      if (this.BuddyIconDownloadCompleted != null)
        this.BuddyIconDownloadCompleted(this, screenname, ms);
    }

    /// <summary>
    /// Occurs when a buddy icon download fails
    /// </summary>
    public event BuddyIconDownloadFailedHandler BuddyIconDownloadFailed;
    /// <summary>
    /// Raises the <see cref="BuddyIconDownloadFailed"/> event
    /// </summary>
    /// <param name="screenname">The screenname of the attemped icon download</param>
    /// <param name="reason">The reason for the failure</param>
    protected internal void OnBuddyIconDownloadFailed(string screenname, string reason)
    {
      if (this.BuddyIconDownloadFailed != null)
        this.BuddyIconDownloadFailed(this, screenname, reason);
    }
    #endregion

    #region SNAC13 events
    /// <summary>
    /// Occurs when the buddy list has been completely sent by the server
    /// </summary>
    public event ContactListFinishedHandler ContactListFinished;
    /// <summary>
    /// Notifies the server to activate the SSI data for the client, and to begin
    /// alerting its contacts that it is now online and ready to receive messages
	/// 
	/// Implementing clients should call <see cref="ActivateBuddyList"/> in response to this event
    /// </summary>
    protected internal void OnContactListFinished()
    {
      if (this.ContactListFinished != null)
        this.ContactListFinished(this);
    }

    /// <summary>
    /// Occurs when the server sends a new buddy item to the client
    /// </summary>
    public event BuddyItemReceivedHandler BuddyItemReceived;
    /// <summary>
    /// Raises the <see cref="BuddyItemReceived"/> event
    /// </summary>
    /// <param name="buddy">An <see cref="SSIBuddy"/> object</param>
    protected internal void OnBuddyItemReceived(SSIBuddy buddy)
    {
      if (this.BuddyItemReceived != null)
        this.BuddyItemReceived(this, buddy);
    }

    /// <summary>
    /// Occurs when the server sends a new group item to the client
    /// </summary>
    public event GroupItemReceivedHandler GroupItemReceived;
    /// <summary>
    /// Raises the <see cref="GroupItemReceived"/> event
    /// </summary>
    /// <param name="group">An <see cref="SSIGroup"/>"/> object</param>
    protected internal void OnGroupItemReceived(SSIGroup group)
    {
      if (this.GroupItemReceived != null)
        this.GroupItemReceived(this, group);
    }
   
    /// <summary>
    /// Occurs when the server sends the master group item to the client
    /// </summary>
    public event MasterGroupItemReceivedHandler MasterGroupItemReceived;
    /// <summary>
    /// Raises the <see cref="MasterGroupItemReceived"/> event
    /// </summary>
    /// <param name="numgroups">The number of groups we are going to receive</param>
    protected internal void OnMasterGroupItemReceived(int numgroups)
    {
      if (this.MasterGroupItemReceived != null)
        this.MasterGroupItemReceived(this, numgroups);
    }

    /// <summary>
    /// Occurs when the an SSI edit is completed
    /// </summary>
    public event SSIEditCompleteHandler SSIEditComplete;
    /// <summary>
    /// Raises the <see cref="SSIEditComplete"/> event
    /// </summary>
    protected internal void OnSSIEditComplete()
    {
      if (this.SSIEditComplete != null)
      {
        this.SSIEditComplete(this);
      }
    }
    #endregion

    #region SNAC17 events
    /// <summary>
    /// Occurs when the login sequence fails
    /// </summary>
    public event LoginFailedHandler LoginFailed;
    /// <summary>
    /// Raises the <see cref="LoginFailed"/> event
    /// </summary>
    /// <param name="reason">A description of the failure</param>
    protected internal void OnLoginFailed(LoginErrorCode errorcode)
    {
      if (this.LoginFailed != null)
        this.LoginFailed(this, errorcode);
    }
    #endregion

    #region Direct Conncetion events
    /// <summary>
    /// Occurs during a file transfer to indicate transfer progression
    /// </summary>
    public event FileTransferProgressHandler FileTransferProgress;

    /// <summary>
    /// Raises the <see cref="FileTransferProgress"/> event
    /// </summary>
    /// <param name="filename">The rendezvous cookie belonging to the file being transfered</param>
    /// <param name="bytestransfered">The number of bytes transfered so far</param>
    /// <param name="bytestotal">The total number of bytes to be transfered</param>
    protected internal void OnFileTransferProgress(byte[] cookie,
      uint bytestransfered, uint bytestotal)
    {
      if (this.FileTransferProgress != null)
      {
        this.FileTransferProgress(this, cookie, bytestransfered, bytestotal);
      }
    }

    /// <summary>
    /// Occurs when a file transfer has been cancelled
    /// </summary>
    public event FileTransferCancelledHandler FileTransferCancelled;

    /// <summary>
    /// Raises the <see cref="FileTransferCancelled"/> event
    /// </summary>
    /// <param name="filename">The rendezvous cookie belonging to the cancelled file</param>
    /// <param name="reason">The reason for the cancellation</param>
    protected internal void OnFileTransferCancelled(byte[] cookie, string reason)
    {
      if (this.FileTransferCancelled != null)
      {
        this.FileTransferCancelled(this, cookie, reason);
      }
    }

    /// <summary>
    /// Occurs when a file transfer has completed
    /// </summary>
    public event FileTransferCompletedHandler FileTransferCompleted;

    /// <summary>
    /// Raises the <see cref="FileTransferCompleted"/> event
    /// </summary>
    /// <param name="filename">The rendezvous cookie belonging to the completed file</param>
    protected internal void OnFileTransferCompleted(byte[] cookie)
    {
      if (this.FileTransferCompleted != null)
      {
        this.FileTransferCompleted(this, cookie);
      }
    }

    /// <summary>
    /// Occurs when a Direct IM has been received
    /// </summary>
    public event DirectIMReceivedHandler DirectIMReceived;
    /// <summary>
    /// Raises the <see cref="DirectIMReceived"/> event
    /// </summary>
    /// <param name="message">The <see cref="DirectIM"/> received</param>
    protected internal void OnDirectIMReceived(DirectIM message)
    {
      if (this.DirectIMReceived != null)
      {
        this.DirectIMReceived(this, message);
      }
    }
    #endregion
    #endregion

    /// <summary>
    /// Create a new OSCAR session
    /// </summary>
    /// <param name="screenname">The screenname to log in</param>
    /// <param name="password">The password associated with the screenname</param>
    /// <exception cref="ArgumentException">Thrown when <paramref name="screenname"/> is not
    /// a valid AIM or ICQ screenname.</exception>
    public Session(string screenname, string password)
    {
      // Check to make sure the screenname is something valid
      if (!ScreennameVerifier.IsValidAIM(screenname) &&
        !ScreennameVerifier.IsValidICQ(screenname))
      {
        throw new ArgumentException(screenname + " is not a valid AIM or ICQ screenname", "screenname");
      }

      // Save parameter values
      _screenname = screenname;
      _password = password;

      _socketfactory = new ProxiedSocketFactoryDelegate(Session.DirectSocketConnectionFactory);
      _connman = new ConnectionManager(this);
      _ssiman = new SSIManager(this);
      _connman.CreateNewConnection(0x0017);

      // Create a default set of capabilities for this session
      SetDefaultIdentification();

      // Set up some default values for public properties
      _publicidletime = true;
      _profile = "";
      _profileenc = System.Text.Encoding.ASCII;
      _available = "";

      // Set initial values for internal properties
      _loggedin = false;
      _privacy = PrivacySetting.AllowAllUsers;
      _parametercount = 0;
      _requestID = 0;

      _ref = this;
    }


    #region Public methods
    /// <summary>
    /// Sets the session's <see cref="ClientIdentification"/> to the AOL defaults
    /// </summary>
    /// <exception cref="LoggedInException">Thrown when the <see cref="Session"/> is already logged in</exception>
    public void SetDefaultIdentification()
    {
      if (LoggedIn)
      {
        throw new LoggedInException("Identification cannot be changed after the session is logged in");
      }
      _clientid.ClientName = Constants.CLIENT_NAME;
      _clientid.ClientId = Constants.CLIENT_ID;
      _clientid.ClientMajor = Constants.CLIENT_MAJOR;
      _clientid.ClientMinor = Constants.CLIENT_MINOR;
      _clientid.ClientLesser = Constants.CLIENT_LESSER;
      _clientid.ClientBuild = Constants.CLIENT_BUILD;
      _clientid.ClientDistribution = Constants.CLIENT_DISTRIBUTION;
    }

    /// <summary>
    /// Initialize the logging system
    /// </summary>
    public void InitializeLogger()
    {
      // Initialize the logging system
      string logfile = DateTime.Now.ToString().Replace("/", "").Replace(":", "");
      while (System.IO.File.Exists(logfile + ".OscarLib.log"))
      {
        logfile += "_";
      }
      logfile += ".OscarLib.log";
      Logging.sw = new System.IO.StreamWriter(logfile);
    }

    /// <summary>
    /// Begins the process of logging in to the OSCAR service
    /// </summary>
    /// <param name="loginserver">The OSCAR login server</param>
    /// <param name="port">The OSCAR service port</param>
    /// <remarks>
    /// <para>
    /// This function is non-blocking, because the login process does not happen
    /// instantly. The OSCAR library will raise the <see cref="Session.LoginCompleted"/> event
    /// when the login process has finished successfully.
    /// </para>
    /// <para>
    /// The OSCAR library raises periodic status update events throughout the login process
    /// via the <see cref="Session.StatusUpdate"/> event.
    /// </para>
    /// <para>
    /// Errors may occur during the login process;  if an error occurs, the OSCAR library raises
    /// the <see cref="Session.ErrorMessage"/> event, and stops the remaining login sequence.
    /// </para>
    /// </remarks>
    /// <exception cref="LoggedInException">Thrown when the <see cref="Session"/> is already logged in</exception>
    public void Logon(string loginserver, int port)
    {
      if (LoggedIn)
      {
        throw new LoggedInException();
      }

      LoggedIn = false;

      Connection conn = _connman.GetByFamily(0x0017);
      conn.ServerConnnectionCompleted += new ServerConnectionCompletedHandler(conn_ServerConnnectionCompleted);
      conn.Server = loginserver;
      conn.Port = port;
      conn.Cookie = null;
      conn.ConnectToServer();
      this.OnLoginStatusUpdate("Connecting to server", 0.00);
    }

    /// <summary>
    /// This is only connected to the initial login server connection
    /// </summary>
    /// <param name="conn"></param>
    void conn_ServerConnnectionCompleted(Connection conn)
    {
      conn.ReadyForData = true;
      SNAC17.SendMD5Request(this);
      conn.ReadHeader();
    }

    /// <summary>
    /// Disconnects all active OSCAR connections and resets the session
    /// </summary>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public void Logoff()
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      LoggedIn = false;

      foreach (Connection conn in _connman.UniqueConnections())
      {
        conn.DisconnectFromServer(false);
      }
    }

    /// <summary>
    /// Sets the client's idle time.
    /// </summary>
    /// <param name="seconds">The client's idle time, in seconds</param>
    /// <remarks>
    /// <para>
    /// To remove idle status, set <paramref name="seconds"/> to zero.
    /// </para>
    /// <para>
    /// This method should be used only once to set the client as idle,
    /// or to remove idle status. The server updates the client's idle time
    /// automatically.  So, for example, if the client application wants the
    /// user to appear idle after ten minutes, the application should call
    /// this method after 10 minutes of being idle:
    /// <code>Session s;
    /// ...
    /// s.SetIdleTime(600);</code>
    /// The next time this method is called, it should be to remove idle status:
    /// <code>s.SetIdleTime(0);</code>
    /// </para>
    /// </remarks>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public void SetIdleTime(uint seconds)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }
      SNAC01.SetIdleTime(this, seconds);
    }

    /// <summary>
    /// Sets the client's away message
    /// </summary>
    /// <param name="message">The away message to set</param>
    /// <param name="encoding">The <see cref="System.Text.Encoding"/> of the away message</param>
    /// <remarks>
    /// <para>Known encodings are <see cref="System.Text.Encoding.ASCII"/> and 
    /// <see cref="System.Text.Encoding.Unicode"/>
    /// </para>
    /// <para>
    /// The session must be logged in for this function to succeed. Calling SetAwayMessage
    /// before a successful login is achieved (i.e., when the <see cref="Session.LoginCompleted"/> event
    /// is raised) results in an exception.
    /// </para>
    /// </remarks>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public void SetAwayMessage(string message, System.Text.Encoding encoding)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }
      SNAC02.SetProfileAwayMessage(this, null, null, message, encoding);
    }

    /// <summary>
    /// Drops the client's away message
    /// </summary>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public void DropAwayMessage()
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      SNAC02.SetProfileAwayMessage(this, null, null, "", System.Text.Encoding.ASCII);
    }

    /// <summary>
    /// Sets the client's profile
    /// </summary>
    /// <param name="message">The profile message to set</param>
    /// <param name="encoding">The <see cref="System.Text.Encoding"/> of the profile message</param>
    /// <remarks>
    /// <para>Known encodings are <see cref="System.Text.Encoding.ASCII"/> and 
    /// <see cref="System.Text.Encoding.Unicode"/>
    /// </para>
    /// <para>
    /// If the session is logged in at the time SetProfile is called, the profile is
    /// reset immediately. Otherwise, it will be set once the session is logged in.
    /// </para>
    /// </remarks>
    public void SetProfile(string message, System.Text.Encoding encoding)
    {
      _profile = message;
      _profileenc = encoding;

      if (LoggedIn)
      {
        SNAC02.SetProfileAwayMessage(this, message, encoding, null, null);
      }
    }

    /// <summary>
    /// Sets the client's available message
    /// </summary>
    /// <param name="message">The available message to set</param>
    /// <remarks>
    /// <para>
    /// If the session is logged in at the time SetAvailableMessage is called, the
    /// available message is reset immediately. Otherwise, it will be set once the
    /// session is logged in.
    /// </para>
    /// </remarks>
    public void SetAvailableMessage(string message)
    {
      _available = message;

      if (LoggedIn)
      {
        SNAC01.SetAvailableMessage(this, message);
      }
    }

    /// <summary>
    /// Requests long information about a user
    /// </summary>
    /// <param name="screenname">The user to get information about</param>
    /// <param name="type">The type of information to request</param>
    /// <remarks>Results are returned by the <see cref="UserInfoReceived"/> event</remarks>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public void RequestUserInfo(string screenname, UserInfoRequest type)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      if (ScreennameVerifier.IsValidICQ(screenname))
      {
        SNAC02.RequestBasicUserInfo(this, screenname, BasicUserInfoRequest.AwayMessage);
      }
      else
      {
        SNAC02.RequestUserInfo(this, screenname, type);
      }
    }

    /// <summary>
    /// Requests basic user information
    /// </summary>
    /// <param name="screenname">The user to get information about</param>
    /// <param name="type">The type of information to request</param>
    /// <remarks>
    /// <para>Results are returned by the <see cref="UserInfoReceived"/> event</para>
    /// <para>Gaim uses this method of user info request exclusively</para>
    /// </remarks>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public void RequestBasicUserInfo(string screenname, BasicUserInfoRequest type)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      SNAC02.RequestBasicUserInfo(this, screenname, type);
    }

    /// <summary>
    /// Requests a list of accounts associated with an email address
    /// </summary>
    /// <param name="email">The email address to use while searching</param>
    /// <throws cref="System.Exception">Thrown when the session is not logged in</throws>
    /// <remarks>Results are returned by the <see cref="SearchByEmailResults"/> event</remarks>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public void SearchUsersByEmail(string email)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      SNAC0A.FindUsersByEmail(this, email);
    }

    /// <summary>
    /// Sends an invitation to join AIM
    /// </summary>
    /// <param name="email">The email address of the person to invite</param>
    /// <param name="text">The text of the invitation</param>
    /// <remarks>
    /// The libfaim documentation contains this delightful vignette:
    /// <code>Once upon a time, there used to be a menu item in AIM clients that
    /// said something like "Invite a friend to use AIM..." and then it would
    /// ask for an email address and it would sent a mail to them saying
    /// how perfectly wonderful the AIM service is and why you should use it
    /// and click here if you hate the person who sent this to you and want to
    /// complain and yell at them in a small box with pretty fonts.</code>
    /// </remarks>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public void SendAIMInvitation(string email, string text)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      SNAC06.SendInvitiationRequest(this, email, text);
    }

    #region SNAC04 methods
    /// <summary>
    /// Sends an invitation to a chat room
    /// </summary>
    /// <param name="destination">The screenname of the person to invite</param>
    /// <param name="message">A message to accompany the request</param>
    /// <param name="roominfo">A <see cref="ChatRoomInfo"/> object describing the chat room</param>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    /// <remarks><paramref name="roominfo"/> comes from the <see cref="ChatRoomCreated"/> event</remarks>
    public void SendChatInvitation(
      string destination,
      string message,
      ChatRoomInfo roominfo)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      SNAC04.SendChatInvitation(this, destination, message, roominfo);
    }

    /// <summary>
    /// Sends an instant message
    /// </summary>
    /// <param name="destination">The screenname to receive the IM</param>
    /// <param name="message">The message to send</param>
    /// <param name="flags">A <see cref="MessageFlags"/> enumeration specifying what
    /// additional information should be sent with the message</param>
    /// <remarks>If delivery confirmation is requested by setting the <see cref="MessageFlags.RequestAcknowledgement"/>
    /// flag, results will be returned in the <see cref="MessageAccepted"/> event.  If the message is not deliverable
    /// for any reason, the <see cref="UndeliverableMessage"/> event will be raised.</remarks>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public void SendMessage(string destination, string message, Encoding encoding, MessageFlags flags)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      SNAC04.SendMessage(this, destination, message, encoding, flags);
    }

    /// <summary>
    /// Sends a typing notification
    /// </summary>
    /// <param name="destination">The screenname to receive the typing notification</param>
    /// <param name="tn">A <see cref="TypingNotification"/> enumeration specifying what
    /// notification to send</param>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public void SendTypingNotification(string destination, TypingNotification tn)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      SNAC04.SendTypingNotification(this, destination, tn);
    }
    #endregion

    /// <summary>
    /// Adds a buddy to the client's server-side buddy list
    /// </summary>
    /// <param name="screenname">The screenname of the buddy to add</param>
    /// <param name="parentID">The ID of the parent group of the buddy</param>
    /// <param name="index">The index of the buddy in the group</param>
    /// <param name="alias">The alias of the buddy ("" for none)</param>
    /// <param name="email">The email address of the buddy ("" for none)</param>
    /// <param name="comment">The comment to be stored for the buddy ("" for none)</param>
    /// <param name="SMS">The SMS number for the buddy ("" for none)</param>
    /// <param name="soundfile">The soundfile for the buddy ("" for none)</param>
    /// <param name="authentication"><c>true</c> if we require authorization for this buddy, <c>false</c> otherwise</param>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    /// <remarks>This function will probably not remain here; the SSI Manager will be made public</remarks>
    public void AddBuddy(string screenname, ushort parentID, int index, string alias, string email, string comment, string SMS, string soundfile, bool authentication)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      this.SSI.AddBuddy(screenname, parentID, index, alias, email, SMS, comment, soundfile);
    }

    /// <summary>
    /// Moves a buddy
    /// </summary>
    /// <param name="buddyID">The ID of the buddy to move</param>
    /// <param name="parentID">The ID of the destination group</param>
    /// <param name="index">The index in the destination group to move to</param>
    public void MoveBuddy(ushort buddyID, ushort parentID, int index)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      this.SSI.MoveBuddy(this.SSI.GetBuddyByID(buddyID), this.SSI.GetGroupByID(parentID), index);
    }

    /// <summary>
    /// Remove a buddy
    /// </summary>
    /// <param name="buddyID">The ID of the buddy to remove</param>
    public void RemoveBuddy(ushort buddyID)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      this.SSI.RemoveBuddy(this.SSI.GetBuddyByID(buddyID));
    }

    /// <summary>
    /// Adds a group to the client's server-side buddy list
    /// </summary>
    /// <param name="groupname">The name of the new group</param>
    /// <param name="index">The index into the current list of groups</param>
    public void AddGroup(string groupname, int index)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      this.SSI.AddGroup(groupname, index);
    }

    /// <summary>
    /// Move a group in the buddy list
    /// </summary>
    /// <param name="groupID">The ID of the group to move</param>
    /// <param name="index">The new index of the group</param>
    public void MoveGroup(ushort groupID, int index)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      this.SSI.MoveGroup(this.SSI.GetGroupByID(groupID), index);
    }

    /// <summary>
    /// Remove a group from the server-side buddy list
    /// </summary>
    /// <param name="groupID">ID of the group to remove</param>
    public void RemoveGroup(ushort groupID)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      this.SSI.RemoveGroup(this.SSI.GetGroupByID(groupID));
    }

	/// <summary>
	/// Tells AIM to begin sending UserStatus objects to client (online or away)
	/// Client should call in response to <see cref="ContactListFinished"/> event
	/// </summary>
    public void ActivateBuddyList()
    {
      SNAC13.ActivateSSI(this);
      OnLoginComplete();
    }

    /// <summary>
    /// Requests a list of user interests from the server
    /// </summary>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public void RequestInterestsList()
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      SNAC0F.RequestInterestList(this);
    }

    /// <summary>
    /// Sends a client status report to the server
    /// </summary>
    /// <remarks>
    /// <para>
    /// This report contains generic information about the client's machine. Its contents are as follows:
    /// <list>
    /// <item>The time the report was sent</item>
    /// <item>The client's screenname</item>
    /// <item>The name and version of Windows on this machine</item>
    /// <item>The name of this machine's processor</item>
    /// <item>The name and version of this machine's Winsock library</item>
    /// </list>
    /// </para>
    /// <para>This report should be sent at regular intervals by the client application. The minimum required
    /// reporting interval is sent by the server during login and received by the
    /// <see cref="ReportingIntervalReceived"/> event</para>
    /// </remarks>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public void SendStatusReport()
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      SNAC0B.SendStatusReport(this);
    }

    #region Icon methods
    /// <summary>
    /// Sets the user's buddy icon
    /// </summary>
    /// <param name="filename">The filename of the image to use as the icon</param>
    /// <remarks>
    /// <para>TODO: The icon filename can be set at any time; however, if the session is not logged in,
    /// and no icon already exists on the server, it is possible that the icon will not be automatically set.
    /// As of release 0.1 beta, this remains a bug to work out.</para>
    /// <para>
    /// AOL has set restrictions for the format and dimensions of a buddy icon. The format
    /// must be JPEG, GIF, BMP, or ICO, and it must be between 48x48 and 50x50 pixels in dimension.
    /// </para>
    /// </remarks>
    /// <exception cref="System.ArgumentException">Thrown when the icon file does not meet
    /// AOL's parameters for a buddy icon. See remarks for details.</exception>
    public void SetBuddyIcon(string filename)
    {
      if (!IconVerifier.CheckIcon(filename))
      {
        throw new ArgumentException("The file " + filename + " does not meet AOL's" +
          " parameters for a buddy icon");
      }

      this.UploadIconFilename = filename;

      if (LoggedIn)
      {
        if (String.IsNullOrEmpty(filename))
        {
          SSI.RemoveIconHash();
        }
        else if (SSI.IconItem == null || SSI.IconItem.ComputeItems(filename))
        {
          SSI.UpdateIconHash();
        }
      }
    }

    /// <summary>
    /// Requests a remote client's buddy icon
    /// </summary>
    /// <param name="screenname">The screenname of the client</param>
    /// <param name="icon">The <see cref="IconInfo"/> of the client's icon, received from the
    /// <see cref="UserStatusReceived"/> event</param>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public void GetBuddyIcon(string screenname, IconInfo icon)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      SNAC10.RequestIcon(this, screenname, icon);
    }
    #endregion

    #region File transfer methods
    /// <summary>
    /// Send a file to a remote client via a direct connection
    /// </summary>
    /// <param name="recipient">The screenname of the remote client</param>
    /// <param name="filename">The path of the file to send</param>
    /// <returns>A key with which to reference this file transfer, or "" if a warning was
    /// generated during the initialization process</returns>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public byte[] SendFile(string recipient, string filename)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      FileTransferConnection ftconn = Connections.CreateNewFileTransferConnection(DirectConnectionMethod.Direct, DirectConnectRole.Initiator);
      ftconn.Other.ScreenName = recipient;
      ftconn.LocalFileName = filename;
      ftconn.ConnectToServer();
      return ftconn.Cookie;
    }

    /// <summary>
    /// Start a DirectIM session with a remote client via a direct connection
    /// </summary>
    /// <param name="recipient">The screenname of the remote client</param>
    /// <returns>A key with which to reference this DirectIM session, or "" if a warning was
    /// generated during the initialization process</returns>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public string StartDirectIM(string recipient)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      throw new NotImplementedException();
      //return _ftman.StartDirectImage(recipient, DirectConnectionMethod.Direct);
    }

    /// <summary>
    /// Send a file to a remote client via an AOL proxy
    /// </summary>
    /// <param name="recipient">The screenname of the remote client</param>
    /// <param name="filename">The path of the file to send</param>
    /// <returns>A key with which to reference this file transfer, or "" if a warning was
    /// generated during the initialization process</returns>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public byte[] SendFileProxied(string recipient, string filename)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      FileTransferConnection newconn = Connections.CreateNewFileTransferConnection(DirectConnectionMethod.Proxied, DirectConnectRole.Initiator);
      newconn.LocalFileName = filename;
      newconn.ConnectToServer();
      return newconn.Cookie;
    }

    /// <summary>
    /// Start a DirectIM session with a remote client via an AOL proxy
    /// </summary>
    /// <param name="recipient">The screenname of the remote client</param>
    /// <returns>A key with which to reference this DirectIM session, or "" if a warning was
    /// generated during the initialization process</returns>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    public string StartDirectIMProxied(string recipient)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      throw new NotImplementedException();
      //return _ftman.StartDirectImage(recipient, DirectConnectionMethod.Proxied);
    }

    /// <summary>
    /// Accept a file being sent to the client
    /// </summary>
    /// <param name="key">The key received in the <see cref="FileTransferRequestReceived"/> event</param>
    /// <param name="savelocation">The path to which to save the file</param>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    /// <exception cref="System.Exception">Thrown when <paramref name="key"/> is not a valid file transfer key</exception>
    public void AcceptFileTransfer(byte[] key, string savelocation)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      FileTransferConnection conn = Connections.GetDirectConnectionByCookie(key) as FileTransferConnection;
      if (conn != null)
      {
        conn.LocalFileName = savelocation;
        conn.ConnectToServer();
      }
      else
      {
        throw new Exception("Invalid file transfer key: \"" + key + "\"");
      }
    }

    /// <summary>
    /// Cancel a pending or in-progress file transfer
    /// </summary>
    /// <param name="key">The key received with the transfer request</param>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    /// <exception cref="System.Exception">Thrown when <paramref name="key"/> is not a valid file transfer key</exception>
    public void CancelFileTransfer(byte[] key)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      FileTransferConnection conn = Connections.GetDirectConnectionByCookie(key) as FileTransferConnection;
      if (conn != null)
      {
        conn.CancelFileTransfer("User cancelled transfer");
      }
    }
    #endregion

    #region ICQ methods
    public void SetICQInfo(ICQInfo info)
    {
      if (!ScreennameVerifier.IsValidICQ(this.ScreenName))
      {
        throw new NotSupportedException("Cannot set ICQ info on a non-ICQ account");
      }

      // TODO:  Assign info struct, perhaps doing a diff with
      // an existing structure and uploading the changes...
      // need to check on that functionality
    }
    #endregion

    #region Chat methods
    /// <summary>
    /// Creates a new chat room
    /// </summary>
    /// <param name="roomname">The name of the room to create</param>
    /// <param name="language">The language to be used in the room</param>
    /// <param name="charset">The character set to be used in the room</param>
    /// <remarks>
    /// <para>Once the chat room has been created, you are automatically joined to it</para>
    /// </remarks>
    public void CreateChatRoom(string roomname, string language, string charset)
    {
      CreateChatRoom(roomname, 0x0004, language, charset);
    }

    /// <summary>
    /// Creates a new chat room
    /// </summary>
    /// <param name="roomname">The name of the room to create</param>
    /// <param name="exchange">The exchange in which to create the room</param>
    /// <param name="language">The language to be used in the room</param>
    /// <param name="charset">The character set to be used in the room</param>
    /// <remarks>
    /// <para>Once the chat room has been created, you are automatically joined to it</para>
    /// <para>This overload of CreateChatRoom allows the client to specify the exchange number.
    /// TODO:  explain this.</para>
    /// </remarks>
    public void CreateChatRoom(string roomname, ushort exchange, string language, string charset)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      if (_connman.GetByFamily(0x000D) == null)
      {
        SNAC0D.RequestParametersList(this);
      }
      ChatInvitationConnection.CacheChatRoomCreation(roomname, exchange);
      SNAC0D.CreateChatRoom(this, roomname, exchange, language, charset);
    }

    /// <summary>
    /// Disconnects from a chat room
    /// </summary>
    /// <param name="roominfo">The <see cref="ChatRoomInfo"/> object to disconnect</param>
    public void LeaveChatRoom(ChatRoomInfo roominfo)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      roominfo.Connection.DisconnectFromServer(false);
    }

    /// <summary>
    /// Invites an AIM user to an AOL chatroom
    /// </summary>
    /// <param name="chatroom">The <see cref="ChatRoomInfo"/> describing the chatroom</param>
    /// <param name="screenname">The screenname of the user to invite</param>
    /// <param name="message">A message to send along with the invitation</param>
    public void InviteToChatRoom(ChatRoomInfo chatroom, string screenname, string message)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      SNAC04.SendChatInvitation(this, screenname, message, chatroom);
    }

    /// <summary>
    /// Accept an invitation to a chat room
    /// </summary>
    /// <param name="key">The key received in the <see cref="ChatInvitationReceived"/> event</param>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    /// <exception cref="System.Exception">Thrown when <paramref name="key"/> is not a valid chat invitation key</exception>
    public void AcceptChatInvitation(byte[] key)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      ChatInvitationConnection cic = Connections.GetDirectConnectionByCookie(key) as ChatInvitationConnection;
      if (cic == null)
      {
        throw new Exception("Invalid chat invitation key: \"" + key + "\"");
      }

      Connections.RemoveDirectConnection(key);

      if (_connman.GetByFamily(0x000D) == null)
      {
        SNAC0D.RequestParametersList(this);
      }
      // If it's not cached here, the chat room creation event won't fire
      ChatInvitationConnection.CacheChatRoomCreation(cic.ChatRoom.DisplayName, cic.ChatRoom.Exchange);
      SNAC0D.CreateChatRoom(this, cic.ChatRoom.DisplayName, cic.ChatRoom.Exchange, "en", "us-ascii");
    }

    /// <summary>
    /// Declines a chat room invitation
    /// </summary>
    /// <param name="key">The key received in the <see cref="ChatInvitationReceived"/> event</param>
    /// <exception cref="NotLoggedInException">Thrown when the <see cref="Session"/> is not logged in</exception>
    /// <exception cref="System.Exception">Thrown when <paramref name="key"/> is not a valid chat invitation key</exception>
    public void DeclineChatInvitation(byte[] key)
    {
      if (!LoggedIn)
      {
        throw new NotLoggedInException();
      }

      ChatInvitationConnection cic = Connections.GetDirectConnectionByCookie(key) as ChatInvitationConnection;
      if (cic == null)
      {
        throw new Exception("Invalid chat invitation key: \"" + key + "\"");
      }

      Connections.RemoveDirectConnection(key);
      SNAC04.SendDirectConnectionCancellation(this, cic, "User declined invitation");
    }

    /// <summary>
    /// Send a message to a specific chat room
    /// </summary>
    /// <param name="room">A <see cref="ChatRoomInfo"/> to which to send the message</param>
    /// <param name="message">The message to send</param>
    /// <param name="encoding">The text encoding of the message</param>
    /// <param name="language">The 2-character language of the message</param>
    public void SendMessageToChatroom(ChatRoomInfo room, string message, Encoding encoding, string language)
    {
      SNAC0E.SendChatRoomMessage(this, room, message, encoding, language, MessageFlags.None);
    }
    #endregion

    #endregion

    #region Internal methods
    /// <summary>
    /// Gets the next SNAC request ID in the sequence 0 through 2^32 - 1, inclusive
    /// </summary>
    /// <returns>The next SNAC request ID</returns>
    /// <remarks>The request ID sequence wraps around if it is about to overflow</remarks>
    protected internal uint GetNextRequestID()
    {
      lock (this)
      {
        if (_requestID == uint.MaxValue) _requestID = 0;
        return _requestID++;
      }
    }

    //		/// <summary>
    //		/// Gets the next FLAP sequence ID in the sequence 0 through 2^15 - 1, inclusive
    //		/// </summary>
    //		/// <returns>The next FLAP sequence ID</returns>
    //		/// <remarks>The sequence ID series wraps around if it is about to overflow</remarks>
    //		protected internal ushort GetNextFLAPSequence()
    //		{
    //			if(_flap_sequence_num == 0x8000) _flap_sequence_num = 0;
    //			return _flap_sequence_num++;
    //		}

    /// <summary>
    /// Returns an MD5 hash of the client's password, an authorization key, and a constant string
    /// </summary>
    /// <param name="authkey">The authorization key sent by the server</param>
    /// <returns>A 16-byte MD5 hash</returns>
    /// <remarks>
    /// <para>
    /// The hashing process is fairly simple:
    /// <list>
    /// <item>The authorization key is put into a buffer</item>
    /// <item>The password itself is hashed via MD5 and appended to the buffer</item>
    /// <item>The constant string, "AOL Instant Messenger (SM)", is appended to the buffer in plaintext</item>
    /// <item>The entire buffer is MD5 hashed and returned to the caller</item>
    /// </list>
    /// </para>
    /// <para>
    /// This method exists to prevent the password from having to be passed around in a data structure
    /// </para>
    /// </remarks>
    protected internal byte[] HashPassword(byte[] authkey)
    {
      MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
      byte[] pass = md5.ComputeHash(
        Marshal.StringToByteArray(_password, Marshal.ASCII));
      byte[] aimstring = Marshal.StringToByteArray(Constants.AIM_MD5_STRING, Marshal.ASCII);

      byte[] buffer = new byte[authkey.Length + aimstring.Length + pass.Length];
      Marshal.CopyArray(authkey, buffer, 0, 0);
      Marshal.CopyArray(pass, buffer, 0, authkey.Length);
      Marshal.CopyArray(aimstring, buffer, 0, authkey.Length + pass.Length);

      return md5.ComputeHash(buffer);
    }

    /// <summary>
    /// Stores data associated with a SNAC request/reply
    /// </summary>
    /// <param name="requestid">A SNAC request ID</param>
    /// <param name="data">The data to be stored</param>
    protected internal void StoreRequestID(uint requestid, object data)
    {
      _requestidstorage.Add(requestid, data);
    }

    /// <summary>
    /// Retrieves data associated with a SNAC request/reply
    /// </summary>
    /// <param name="requestid">A SNAC request ID</param>
    /// <returns>The data previously stored by <see cref="StoreRequestID"/></returns>
    protected internal object RetrieveRequestID(uint requestid)
    {
      return _requestidstorage[requestid];
    }


    /// <summary>
    /// Sets the session's privacy setting sent by the server in SNAC(13,06)
    /// </summary>
    /// <param name="ps">One of the <see cref="PrivacySetting"/> enumeration members</param>
    protected internal void SetPrivacyFromServer(PrivacySetting ps)
    {
      _privacy = ps;
    }

    /// <summary>
    /// Sets whether or not the client's idle time is public -- SNAC(13,06)
    /// </summary>
    /// <param name="publicidletime">true if others can see this client's idle time, false otherwise</param>
    protected internal void SetPresence(bool publicidletime)
    {
      _publicidletime = publicidletime;
    }

    /// <summary>
    /// Keeps track of the SNAC parameter responses that have been received thus far
    /// </summary>
    protected internal void ParameterSetArrived()
    {
      _parametercount++;

      if (_parametercount == 5)
      {
        // We can send more stuff now
      }
    }
    #endregion

    #region Properties
    /// <summary>
    /// Gets or sets the screen name associated with this session
    /// </summary>
    /// <remarks>
    /// The screen name cannot be set by this property while the client is offline.
    /// When the client is online, setting the screen name by this property changes the
    /// screen name's formatting on the server.
    /// </remarks>
    /// <exception cref="ArgumentException">Thrown when <paramref name="screenname"/> is not
    /// a valid AIM or ICQ screenname.</exception>
    public string ScreenName
    {
      get { return _screenname; }
      set
      {
        if (LoggedIn)
        {
          if (!(ScreennameVerifier.IsValidAIM(value) || ScreennameVerifier.IsValidICQ(value)))
          {
            throw new ArgumentException(value + " is not a valid AIM or ICQ screenname");
          }
          _screenname = value;

          // TODO:  Actually reset the formatting...
        }
      }
    }

    /// <summary>
    /// Gets or sets the port number used for OSCAR logins
    /// </summary>
    /// <remarks>
    /// Traditionally, this is port 5190; however, AIM 6 has been caught using port 443 to negotiate
    /// connections with login.oscar.aol.com and ars.oscar.aol.com.  Future versions of OscarLib may use
    /// this property to support login via port 443.
    /// </remarks>
    public ushort LoginPort
    {
      get { return _loginport; }
      internal set { _loginport = value; }
    }

    /// <summary>
    /// Gets or sets this session's OSCAR identification information
    /// </summary>
    /// <exception cref="LoggedInException">Thrown when the <see cref="Session"/> is already logged in</exception>
    public OSCARIdentification ClientIdentification
    {
      get { return _clientid; }
      set
      {
        if (LoggedIn)
        {
          throw new LoggedInException("Identification cannot be changed after the session is logged in");
        }
        _clientid = value;
      }
    }

    /// <summary>
    /// Gets or sets the OSCAR capabilities associated with the session
    /// </summary>
    /// <remarks>
    /// The client capabilities must be set before the session is logged in because the
    /// client's capabilities are communicated during the login process and are kept through
    /// the session.
    /// </remarks>
    /// <exception cref="LoggedInException">Thrown when the <see cref="Session"/> is already logged in</exception>
    public Capabilities ClientCapabilities
    {
      get { return _caps; }
      set
      {
        if (LoggedIn)
        {
          throw new LoggedInException("Client capabilities cannot be set after the session is logged in");
        }
        _caps = value;
      }
    }

    /// <summary>
    /// Gets the session's profile string
    /// </summary>
    /// <remarks>To set the session's profile string, use the
    /// <see cref="Session.SetProfile"/> method</remarks>
    public string Profile
    {
      get { return _profile; }
    }

    /// <summary>
    /// Gets the encoding of the session's profile string
    /// </summary>
    /// <remarks>To set the encoding of the session's profile string, use the
    /// <see cref="Session.SetProfile"/> method</remarks>
    public System.Text.Encoding ProfileEncoding
    {
      get { return _profileenc; }
    }

    /// <summary>
    /// Gets the session's Available message string
    /// </summary>
    /// <remarks>To set the the session's Available message, use the
    /// <see cref="Session.SetAvailableMessage"/> method</remarks>
    public string AvailableMessage
    {
      get { return _available; }
    }

    /// <summary>
    /// Gets a value indicating whether this client's idle time is publicly available
    /// </summary>
    public bool PublicIdleTime
    {
      get { return _publicidletime; }
    }

    /// <summary>
    /// Gets a value indicating whether this client has completed the login process
    /// </summary>
    public bool LoggedIn
    {
      get { return _loggedin; }
      protected set
      {
        _loggedin = value;
      }
    }

    /// <summary>
    /// Gets the <see cref="SSIManager"/> associated with this session
    /// </summary>
    public SSIManager SSI
    {
      get { return _ssiman; }
    }

    /// <summary>
    /// Gets the <see cref="LimitManager"/> associated with this session
    /// </summary>
    public LimitManager Limits
    {
      get { return _limitman; }
    }

    /// <summary>
    /// Gets or sets a filesystem path where OscarLib can place received data
    /// </summary>
    /// <remarks>During an OSCAR Direct Connect session, "transient" files may come over the wire.
    /// If ScratchPath is set to a valid path, OscarLib will save the files locally and return
    /// <see cref="System.IO.FileStream"/> references to the objects. Otherwise, the files will
    /// be returned as <see cref="System.IO.MemoryStream"/> objects, which will take more active memory.</remarks>
    public string ScratchPath
    {
      get { return _scratchpath; }
      set { _scratchpath = value; }
    }

    /// <summary>
    /// Gets or sets the <see cref="RendezvousManager"/> associated with this session
    /// </summary>
    internal RendezvousManager Rendezvous
    {
      get { return _rdman; }
      set { _rdman = value; }
    }

    /// <summary>
    /// Gets or sets the <see cref="ConnectionManager"/> associated with this session
    /// </summary>
    internal ConnectionManager Connections
    {
      get { return _connman; }
      set { _connman = value; }
    }

    /// <summary>
    /// Gets or sets the <see cref="FamilyManager"/> associated with this session
    /// </summary>
    internal FamilyManager Families
    {
      get { return _famman; }
      set { _famman = value; }
    }

    /// <summary>
    /// Gets or sets the <see cref="RateClassManager"/> associated with this session
    /// </summary>
    internal RateClassManager RateClasses
    {
      get { return _rateman; }
      set { _rateman = value; }
    }

    /// <summary>
    /// Gets or sets the local filename of the icon being uploaded
    /// </summary>
    protected internal string UploadIconFilename
    {
      get { return _iconfilename; }
      set { _iconfilename = value; }
    }

    protected internal static bool SessionLoginComplete
    {
      get
      {
        return _ref.LoggedIn;
      }
    }

    private ProxiedSocketFactoryDelegate _socketfactory = null;
    /// <summary>
    /// Gets or sets the <see cref="ProxiedSocketFactoryDelegate"/> to use to create new socket connections
    /// </summary>
    /// <remarks>By default, this property is set to <see cref="Session.DirectSocketConnectionFactory"/></remarks>
    public ProxiedSocketFactoryDelegate ProxiedSocketFactory
    {
      get { return _socketfactory; }
      set { _socketfactory = value; }
    }
    #endregion

    /// <summary>
    /// Implements an asynchronous socket connection factory that operates with no network proxy
    /// </summary>
    /// <param name="host">The host to which to connect</param>
    /// <param name="port">The port on the host to which to connect</param>
    /// <remarks>This is a public wrapper to the internal method <see cref="Connection.CreateDirectConnectSocket"/></remarks>
    public static void DirectSocketConnectionFactory(string host, int port, Delegate callback)
    {
      Connection.CreateDirectConnectSocket(host, port, callback);
    }
  }
}
